package com.wugh.fast.module.mapper;

import cn.hutool.core.exceptions.ExceptionUtil;
import com.esotericsoftware.reflectasm.ConstructorAccess;
import com.esotericsoftware.reflectasm.MethodAccess;
import com.wugh.fast.module.mapper.interceptor.plugin.CustomizePlugin;
import com.wugh.fast.module.mapper.interceptor.plugin.impl.IsDeletedPlugin;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ArrayUtils;
import tk.mybatis.mapper.entity.EntityColumn;
import tk.mybatis.mapper.entity.EntityTable;
import tk.mybatis.mapper.entity.Example;
import tk.mybatis.mapper.entity.IDynamicTableName;
import tk.mybatis.mapper.mapperhelper.EntityHelper;
import tk.mybatis.mapper.weekend.Weekend;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.BiConsumer;
import java.util.function.Function;

/**
 * @author WUGH
 * @date 2018/1/31
 */
@Slf4j
@Data
public class EntityInfo<T> {

    private Class<T> entityClass;
    private MethodAccess methodAccess;
    private ConstructorAccess<T> constructorAccess;
    private EntityTable entityTable;
    private IsDeletedPlugin isDeletedPlugin;
    private Map<String, Function<T, Object>> getterMap;
    private Map<String, BiConsumer<T, Object>> setterMap;
    private EntityColumn pkColumn;
    private List<CustomizePlugin> plugins;

    public EntityInfo(Class<T> entityClass, String[] pluginClasses) {
        this.entityClass = entityClass;
        this.entityTable = EntityHelper.getEntityTable(this.entityClass);
        this.methodAccess = MethodAccess.get(entityClass);
        this.constructorAccess = ConstructorAccess.get(entityClass);
        Set<EntityColumn> entityClassColumns = this.entityTable.getEntityClassColumns();
        this.getterMap = new ConcurrentHashMap<>(entityClassColumns.size());
        this.setterMap = new ConcurrentHashMap<>(entityClassColumns.size());
        for (EntityColumn column : entityClassColumns) {
            String property = column.getEntityField().getName();
            String name = upperCaseFirst(property);
            Integer getterIndex = this.methodAccess.getIndex("get" + name);
            getterMap.put(property, record -> this.methodAccess.invoke(record, getterIndex));
            Integer setterIndex = this.methodAccess.getIndex("set" + name, column.getJavaType());
            setterMap.put(property, (record, value) -> this.methodAccess.invoke(record, setterIndex, value));
        }
        Set<EntityColumn> pkColumns = this.entityTable.getEntityClassPKColumns();
        for (EntityColumn pkColumn : pkColumns) {
            this.pkColumn = pkColumn;
            break;
        }
        if (this.pkColumn == null) {
            //缺少主键
            throw new RuntimeException("缺少主键");
        }
        if (ArrayUtils.isNotEmpty(pluginClasses)) {
            plugins = new ArrayList<>();
            for (String className : pluginClasses) {
                try {
                    Class<? extends CustomizePlugin> clazz = (Class<? extends CustomizePlugin>) Class.forName(className);
                    CustomizePlugin plugin = clazz.newInstance();
                    if (plugin.support(this)) {
                        plugins.add(plugin);
                        if (plugin instanceof IsDeletedPlugin) {
                            this.isDeletedPlugin = (IsDeletedPlugin) plugin;
                        }
                    }
                } catch (Exception e) {
                    log.error("插件[{}]创建失败:{}", className, ExceptionUtil.getRootCauseMessage(e));
                }
            }
            //排序
            plugins.sort(Comparator.comparing(CustomizePlugin::getIndex));
        }
    }


    public Example getExampleByRecord(T record) {
        Example example = this.newExample();
        Example.Criteria criteria = example.createCriteria();
        Set<EntityColumn> columns = this.getEntityTable().getEntityClassColumns();
        for (EntityColumn column : columns) {
            Object value = getValue(column.getEntityField().getName(), record);
            if (value != null) {
                criteria.andEqualTo(column.getProperty(), value);
            }
        }
        return example;
    }

    public Example newExample() {
        Example example = new Example(this.entityClass, true, true);
        setTableName(example);
        return example;
    }

    private void setTableName(Example example) {
        if (IDynamicTableName.class.isAssignableFrom(this.entityClass)) {
            try {
                IDynamicTableName instance = (IDynamicTableName) this.entityClass.newInstance();
                String dynamicTableName = instance.getDynamicTableName();
                example.setTableName(dynamicTableName);
            } catch (Exception e) {
                log.warn("{}通过IDynamicTableName#getDynamicTableName获取动态表名失败:{}", this.entityClass.getName(), ExceptionUtil.getRootCauseMessage(e));
            }
        }
    }

    public Weekend<T> newWeekend() {
        Weekend<T> of = Weekend.of(this.entityClass, true, true);
        setTableName(of);
        return of;
    }

    public String getPkFieldName() {
        return this.pkColumn.getEntityField().getName();
    }

    public void setPKColumn(Example example, Object key) {
        Example.Criteria criteria = example.and();
        Set<EntityColumn> pkColumns = entityTable.getEntityClassPKColumns();
        for (EntityColumn pkColumn : pkColumns) {
            criteria.andEqualTo(pkColumn.getProperty(), key);
        }
    }

    public Example getPKExample(Object key) {
        Example example = newExample();
        setPKColumn(example, key);
        return example;
    }

    public Example getPKExampleByRecord(T record) {
        Example example = newExample();
        Set<EntityColumn> pkColumns = this.entityTable.getEntityClassPKColumns();
        for (EntityColumn pkColumn : pkColumns) {
            Object value = getValue(pkColumn.getEntityField().getName(), record);
            setPKColumn(example, value);
        }
        return example;
    }

    public Example getRecordExample(T record) {
        Example example = newExample();
        Example.Criteria criteria = example.and();
        this.getGetterMap().forEach((property, getter) -> {
            Object value = getter.apply(record);
            if (value != null) {
                criteria.andEqualTo(property, value);
            }
        });
        return example;
    }

    private String upperCaseFirst(String name) {
        if (name.length() > 1) {
            name = name.substring(0, 1).toUpperCase() + name.substring(1);
        } else {
            name = name.toUpperCase();
        }
        return name;
    }

    public Object getValue(String property, T record) {
        Function<T, Object> getter = this.getPropertyGetter(property);
        return getter.apply(record);
    }

    public void setValue(String property, T record, Object value) {
        BiConsumer<T, Object> setter = this.getPropertySetter(property);
        setter.accept(record, value);
    }

    public <R> Function<T, R> getPropertyGetter(String property) {
        return (Function<T, R>) this.getterMap.get(property);
    }

    public <U> BiConsumer<T, U> getPropertySetter(String property) {
        return (BiConsumer<T, U>) this.setterMap.get(property);
    }

    /**
     * 是否有逻辑删除字段
     * 判断是否是有逻辑删除插件
     *
     * @return
     */
    public boolean hasIsDeleted() {
        //有isDeleted插件,并且插件正在使用
        return isDeletedPlugin != null && isDeletedPlugin.isWork();
    }

    public T getDeletedRecord() {
        if (hasIsDeleted()) {
            return (T) isDeletedPlugin.getDeletedRecord();
        }
        return null;
    }
}
